// MPW 加密工具函数 TypeScript 实现

// MPW 加密工具函数 TypeScript 实现
import type { PBKDF2Function, ScryptFunction } from './mpw-types';

/**
 * 设置 setImmediate polyfill
 */
export function setupSetImmediate(): void {
  if (!window.setImmediate) {
    window.setImmediate = (callback: () => void) => setTimeout(callback, 0);
  }
}

/**
 * PBKDF2 实现
 */
export function createPBKDF2(): PBKDF2Function {
  // 纯 JavaScript 实现的 PBKDF2
  function pbkdf2JS(
    password: Uint8Array,
    salt: Uint8Array,
    iterations: number,
    keyLen: number,
    hash: string
  ): Promise<Uint8Array> {
    const hashName = hash.toUpperCase();
    let hashLen: number;

    switch (hashName) {
      case 'SHA1':
      case 'SHA-1':
        hashLen = 160 / 8;
        break;
      case 'SHA224':
      case 'SHA-224':
        hashLen = 224 / 8;
        break;
      case 'SHA256':
      case 'SHA-256':
        hashLen = 256 / 8;
        break;
      case 'SHA384':
      case 'SHA-384':
        hashLen = 384 / 8;
        break;
      case 'SHA512':
      case 'SHA-512':
        hashLen = 512 / 8;
        break;
      default:
        const err = new Error("A parameter or an operation is not supported by the underlying object");
        err.name = "InvalidAccessError";
        return Promise.reject(err);
    }

    const numBlocks = Math.ceil(keyLen / hashLen);
    const data = new Uint8Array(salt.length + 4);
    const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);

    data.set(salt);

    return window.crypto.subtle.importKey("raw", password, {
      name: "HMAC",
      hash: hash
    }, false, ["sign"]).then((key) => {
      const dk = new Uint8Array(numBlocks * hashLen);
      let promise: Promise<ArrayBuffer> = Promise.resolve(new ArrayBuffer(0));

      for (let block = 1, dki = 0; block <= numBlocks; block++, dki += hashLen) {
        promise = promise
          .then(() => {
            dataView.setUint32(salt.length, block, false);
            return window.crypto.subtle.sign({
              name: "HMAC",
              hash: hash
            }, key, data);
          })
          .then((pdk) => {
            dk.set(new Uint8Array(pdk), dki);
            return pdk;
          });

        for (let n = 2; n <= iterations; n++) {
          promise = promise
            .then((U) => window.crypto.subtle.sign({
              name: "HMAC",
              hash: hash
            }, key, U))
            .then((U) => {
              const Ux = new Uint8Array(U);
              for (let i = 0; i < Ux.length; i++) {
                dk[dki + i] ^= Ux[i];
              }
              return U;
            });
        }
      }

      return promise.then(() => dk.subarray(0, keyLen));
    });
  }

  // 使用 Web Crypto API 的 PBKDF2 实现
  function pbkdf2WebCrypto(
    password: Uint8Array,
    salt: Uint8Array,
    iterations: number,
    keyLen: number,
    hash: string
  ): Promise<Uint8Array> {
    return window.crypto.subtle.importKey("raw", password, {
      name: "PBKDF2",
      hash: hash
    }, false, ["deriveBits"])
      .then(key => window.crypto.subtle.deriveBits({
        name: "PBKDF2",
        salt: salt,
        iterations: iterations,
        hash: hash
      }, key, keyLen * 8))
      .then(key => new Uint8Array(key))
      .catch(err => {
        // 如果 Web Crypto API 不支持，回退到 JS 实现
        if (err.name === "OperationError" ||
            err.name === "NotSupportedError" ||
            err.name === "InvalidAccessError") {
          return pbkdf2JS(password, salt, iterations, keyLen, hash);
        }
        return Promise.reject(err);
      });
  }

  // 使用 crypto-js 的回退实现
  function pbkdf2CryptoJS(
    password: Uint8Array,
    salt: Uint8Array,
    iterations: number,
    keyLen: number,
    hash: string
  ): Promise<Uint8Array> {
    const hashName = hash.toUpperCase();
    let hashAlg: any;

    if (!window.CryptoJS) {
      return Promise.reject(new Error("CryptoJS not available"));
    }

    switch (hashName) {
      case 'SHA1':
      case 'SHA-1':
        hashAlg = window.CryptoJS.algo.SHA1;
        break;
      case 'SHA224':
      case 'SHA-224':
        hashAlg = window.CryptoJS.algo.SHA224;
        break;
      case 'SHA256':
      case 'SHA-256':
        hashAlg = window.CryptoJS.algo.SHA256;
        break;
      case 'SHA384':
      case 'SHA-384':
        hashAlg = window.CryptoJS.algo.SHA384;
        break;
      case 'SHA512':
      case 'SHA-512':
        hashAlg = window.CryptoJS.algo.SHA512;
        break;
      default:
        const err = new Error("A parameter or an operation is not supported by the underlying object");
        err.name = "InvalidAccessError";
        return Promise.reject(err);
    }

    return new Promise((resolve) => {
      window.setImmediate!(() => {
        const passwordWords = window.CryptoJS.lib.WordArray.create(password);
        const saltWords = window.CryptoJS.lib.WordArray.create(salt);

        const derivedKey = window.CryptoJS.PBKDF2(passwordWords, saltWords, {
          keySize: keyLen * 8 / 32,
          iterations: iterations,
          hasher: hashAlg
        });

        const key = new Uint8Array(derivedKey.words.length * 4);
        const keyView = new DataView(key.buffer, key.byteOffset, key.byteLength);

        for (let i = 0; i < derivedKey.words.length; i++) {
          keyView.setInt32(i * 4, derivedKey.words[i], false);
        }

        resolve(key.subarray(0, keyLen));
      });
    });
  }

  // 返回主要的 PBKDF2 函数
  return function pbkdf2(
    password: Uint8Array,
    salt: Uint8Array,
    iterations: number,
    keyLen: number,
    hash: string
  ): Promise<Uint8Array> {
    if (window.crypto && window.crypto.subtle) {
      return pbkdf2WebCrypto(password, salt, iterations, keyLen, hash);
    } else if (window.CryptoJS) {
      return pbkdf2CryptoJS(password, salt, iterations, keyLen, hash);
    } else {
      return Promise.reject(new Error("No cryptographic implementation available"));
    }
  };
}

/**
 * Scrypt 实现
 */
export function createScrypt(): ScryptFunction {
  // Salsa20/8 核心函数
  function salsaXOR(tmp: Uint32Array, inp: Uint32Array, out: Uint32Array): void {
    const w = new Uint32Array(16);
    const x = new Uint32Array(16);

    for (let i = 0; i < 16; i++) {
      x[i] = w[i] = tmp[i] ^ inp[i];
    }

    for (let i = 0; i < 8; i += 2) {
      let u: number;

      u = (x[0] + x[12]) | 0; x[4] ^= (u << 7) | (u >>> 25);
      u = (x[4] + x[0]) | 0; x[8] ^= (u << 9) | (u >>> 23);
      u = (x[8] + x[4]) | 0; x[12] ^= (u << 13) | (u >>> 19);
      u = (x[12] + x[8]) | 0; x[0] ^= (u << 18) | (u >>> 14);

      u = (x[5] + x[1]) | 0; x[9] ^= (u << 7) | (u >>> 25);
      u = (x[9] + x[5]) | 0; x[13] ^= (u << 9) | (u >>> 23);
      u = (x[13] + x[9]) | 0; x[1] ^= (u << 13) | (u >>> 19);
      u = (x[1] + x[13]) | 0; x[5] ^= (u << 18) | (u >>> 14);

      u = (x[10] + x[6]) | 0; x[14] ^= (u << 7) | (u >>> 25);
      u = (x[14] + x[10]) | 0; x[2] ^= (u << 9) | (u >>> 23);
      u = (x[2] + x[14]) | 0; x[6] ^= (u << 13) | (u >>> 19);
      u = (x[6] + x[2]) | 0; x[10] ^= (u << 18) | (u >>> 14);

      u = (x[15] + x[11]) | 0; x[3] ^= (u << 7) | (u >>> 25);
      u = (x[3] + x[15]) | 0; x[7] ^= (u << 9) | (u >>> 23);
      u = (x[7] + x[3]) | 0; x[11] ^= (u << 13) | (u >>> 19);
      u = (x[11] + x[7]) | 0; x[15] ^= (u << 18) | (u >>> 14);

      u = (x[0] + x[3]) | 0; x[1] ^= (u << 7) | (u >>> 25);
      u = (x[1] + x[0]) | 0; x[2] ^= (u << 9) | (u >>> 23);
      u = (x[2] + x[1]) | 0; x[3] ^= (u << 13) | (u >>> 19);
      u = (x[3] + x[2]) | 0; x[0] ^= (u << 18) | (u >>> 14);

      u = (x[5] + x[4]) | 0; x[6] ^= (u << 7) | (u >>> 25);
      u = (x[6] + x[5]) | 0; x[7] ^= (u << 9) | (u >>> 23);
      u = (x[7] + x[6]) | 0; x[4] ^= (u << 13) | (u >>> 19);
      u = (x[4] + x[7]) | 0; x[5] ^= (u << 18) | (u >>> 14);

      u = (x[10] + x[9]) | 0; x[11] ^= (u << 7) | (u >>> 25);
      u = (x[11] + x[10]) | 0; x[8] ^= (u << 9) | (u >>> 23);
      u = (x[8] + x[11]) | 0; x[9] ^= (u << 13) | (u >>> 19);
      u = (x[9] + x[8]) | 0; x[10] ^= (u << 18) | (u >>> 14);

      u = (x[15] + x[14]) | 0; x[12] ^= (u << 7) | (u >>> 25);
      u = (x[12] + x[15]) | 0; x[13] ^= (u << 9) | (u >>> 23);
      u = (x[13] + x[12]) | 0; x[14] ^= (u << 13) | (u >>> 19);
      u = (x[14] + x[13]) | 0; x[15] ^= (u << 18) | (u >>> 14);
    }

    for (let i = 0; i < 16; i++) {
      out[i] = tmp[i] = (x[i] + w[i]) | 0;
    }
  }

  // BlockMix 函数
  function blockMix(inp: Uint32Array, out: Uint32Array, r: number): void {
    const tmp = inp.slice((2 * r - 1) * 16, (2 * r) * 16);

    for (let i = 0; i < 2 * r; i += 2) {
      salsaXOR(tmp, inp.subarray(i * 16), out.subarray(i * 8));
      salsaXOR(tmp, inp.subarray((i + 1) * 16), out.subarray((i + 2 * r) * 8));
    }
  }

  // SMix 函数
  function smix(
    b: Uint8Array,
    r: number,
    N: number,
    v: Uint32Array,
    x: Uint32Array,
    y: Uint32Array
  ): void {
    const bView = new DataView(b.buffer, b.byteOffset, b.byteLength);

    for (let i = 0, j = 0; i < x.length; i++, j += 4) {
      x[i] = bView.getUint32(j, true);
    }

    for (let i = 0; i < N; i += 2) {
      v.set(x, i * 32 * r);
      blockMix(x, y, r);

      v.set(y, (i + 1) * 32 * r);
      blockMix(y, x, r);
    }

    for (let i = 0; i < N; i += 2) {
      const j = (x[(2 * r - 1) * 16] | (x[(2 * r - 1) * 16 + 1] * 0x100000000)) & (N - 1);

      for (let k = 0; k < x.length; k++) {
        x[k] ^= v[j * 32 * r + k];
      }

      blockMix(x, y, r);

      const j2 = (y[(2 * r - 1) * 16] | (y[(2 * r - 1) * 16 + 1] * 0x100000000)) & (N - 1);

      for (let k = 0; k < y.length; k++) {
        y[k] ^= v[j2 * 32 * r + k];
      }

      blockMix(y, x, r);
    }

    for (let i = 0, j = 0; i < x.length; i++, j += 4) {
      bView.setUint32(j, x[i], true);
    }
  }

  // 主要的 scrypt 函数
  return function scrypt(
    password: Uint8Array,
    salt: Uint8Array,
    N: number,
    r: number,
    p: number,
    keyLen: number
  ): Promise<Uint8Array> {
    // 参数验证
    if (r * p >= Math.pow(2, 30)) {
      return Promise.reject(new Error("Parameters r and p are too large"));
    }

    if (N < 2 || (N & (N - 1)) !== 0 || N > Number.MAX_SAFE_INTEGER) {
      return Promise.reject(new Error("Argument N is invalid; N must be > 1, a power of 2 and less than 2^53"));
    }

    const x = new Uint32Array(32 * r);
    const y = new Uint32Array(32 * r);
    const v = new Uint32Array(32 * N * r);

    const pbkdf2 = window.pbkdf2!;
    let b = pbkdf2(password, salt, 1, p * 128 * r, "SHA-256");

    for (let i = 0; i < p; i++) {
      b = b.then(b => new Promise<Uint8Array>((resolve) => {
        window.setImmediate!(() => {
          smix(b.subarray(i * 128 * r), r, N, v, x, y);
          resolve(b);
        });
      }));
    }

    return b.then(b => pbkdf2(password, b, 1, keyLen, "SHA-256"));
  };
}
